#include "GLFWApp.h"
#include "Shapes3D.h"

#include "fonts/bitmap_font.h"
#include <cstdlib>
#include <map>
#include <string>
#include <list>

#include <utils.hpp>
using siut::simd::Vec3f;
using siut::simd::Mat4f;

#include "verlet_engine.h"


class MyApp : public GLFWApp
{
public:
	MyApp() : GLFWApp()
	{
		init();
		mainloop();
	}

	virtual ~MyApp()
	{
		printf("dtor myapp %s\n",__FILE__);
	}

	void circleOfCircles(real x, real y, int numRings, real circRadi)
	{
		real radi = circRadi;
		int numCircs = 1;
		for(int i=0; i<numRings; i++)
		{
			for(int j=0; j<numCircs; j++)
			{
				if(numCircs>1)
				{
					real ang = 2.0*3.14 * real(j)/real(numCircs);
					real tx = cos(ang)*radi;
					real ty = sin(ang)*radi;
					//drawsph(x+tx,y+ty,-circRadi);
					SpringBody *b = SpringFactory::makeBox( &physWorld, x+tx - circRadi, y+ty - circRadi, circRadi*2,circRadi*2);
					b->calcCenter();
					b->rotate(ang, b->centroid.x(), b->centroid.y() );

				}else
				{
					SpringBody *b = SpringFactory::makeBox( &physWorld, x - circRadi, y - circRadi, circRadi*2,circRadi*2);
				}


			}
			radi += circRadi * 2.5;
			numCircs += 7;
		}
	}

	void setupWorld()
	{
		circleOfCircles(0,0,15,0.5);
	}

	void init()
	{
		printf("press G to toggle grid \n");
		millisHistogram.resize(60);

		zoom = 15;
		camPos.is(0,0);
		mouseBeginPos.is(0,0);

		glfwSwapInterval(1);
		BitmapFont::Init();

		//player = SpringFactory::makeBox( &physWorld, -2.5,-2.5,0.5,0.5 );
		setupWorld();
		
	//	real s = 0.5;
	//	real s2 = s * 0.8;
	//	int numObjs = 36;
	//	int sideBySide = numObjs;
	//	//int sideBySide = sqrt( float(numObjs) );
	//	real halfx = sideBySide * s / 2.0;
	//	real halfy = sideBySide * s / 2.0;
	//	for(int i=0; i<sideBySide; i++)
	//	{
	//		//for(int j=0; j<sideBySide; j++)
	//		for(int j=0; j<10; j++)
	//		{
	//			//real xpos = i * s;
	//			//real ypos = j * s;
	//			real ang = i * 6.28f / real(sideBySide-j);
	//			real radi = 5 - j * s2 * 1.2;
	//			real xpos = cos( ang ) * radi;
	//			real ypos = sin( ang ) * radi;
	//			SpringBody *b = SpringFactory::makeBox( &physWorld, -halfx + xpos, - halfy + ypos, s2,s2);
	//			b->calcCenter();
	//			b->rotate(ang, b->centroid.x(), b->centroid.y() );
	//		}

	//	}

	}

	// \breif Expects OpenGL modelview matrix to be set to world view. for instance with gluLookAt
	void updateWorldMouseCoordinates()
	{
		float ratioX = 16 * zoom * 1.0 / 15.0;
		float ratioY = 12 * zoom * 1.0 / 15.0;

		Vec2f mousePos( Input::mouseX()/(xres/1.0f) * ratioX - ratioX/2.0f, -Input::mouseY()/(yres/1.0f) * ratioY + ratioY/2.0f );
		Vec3f centerOfInterest(0.0,0.0,0.0);
		//Vec3f centerOfInterest( (float)player->centroid.x(), (float)player->centroid.y(), 0.0f );

		float mtx[16];
		glGetFloatv( GL_MODELVIEW_MATRIX, mtx );

		Mat4f mvp( mtx );
		Mat4f mvp_Inv;
		mvp_Inv = inverse(mvp);


		mousePointer = siut::simd::mousePosOnInterestPlaneAsObjectCoords(mvp, mvp_Inv, centerOfInterest, mousePos);
	}


	void update()
	{
		physWorld.startFrame();

		static bool runSim = false;
		if( Input::keyHit('R') ) runSim = !runSim;

		if( Input::keyHit('S') )
		{
			printf("deleting all physbodies...\n");
			std::vector<SpringBody*> bodyListCopy = physWorld.bodyList;
			for each(SpringBody *b in bodyListCopy)
			{
				physWorld.removeBody(b);
			}

			setupWorld();
		}

		double physTimeSpent = glfwGetTime();
		if( runSim ) physWorld.update( dt );
		physTimeSpent = glfwGetTime() - physTimeSpent;

		if( Input::keyHit(GLFW_KEY_ESC) )
		{
			this->gameRunning = false;
		}

		if( Input::keyHit('G') )
		{
			physWorld.toggleGrid();
		}
		


		if( Input::MouseDown(GLFW_MOUSE_BUTTON_LEFT) )
		{
			Vec2f mousePos( mousePointer.x(), mousePointer.y() );
			real minDist = 1e32;
			SpringBody *grabby = 0x0;
			for each( SpringBody* b in physWorld.bodyList )
			{
				double dist = length_2( mousePos - b->centroid );
				if( dist < minDist )
				{
					minDist = dist;
					grabby = b;
				}
			}

			if( grabby )
			{
				grabby->addForce( 0.01 * normalize( mousePos - grabby->centroid ) );
				//grabby->particles[0]->pos = mousePos;
			}
		}

		if( Input::MouseHit(GLFW_MOUSE_BUTTON_RIGHT) )
		{
			mouseBeginPos.is( mousePointer.x(), mousePointer.y() );		
		}
		if( Input::MouseDown(GLFW_MOUSE_BUTTON_RIGHT)  )
		{
			Vec2f mp( mousePointer.x(), mousePointer.y() ) ;

			Vec2f dir = mouseBeginPos - mp;
			real lenSq = length_2(dir);
			if( lenSq > 0 )
			{
				camPos += dir;
			}
		}

		if( Input::MouseHit(GLFW_MOUSE_BUTTON_MIDDLE) )
		{	
			SpringFactory::makeBox( &physWorld, mousePointer.x(), mousePointer.y(), 0.5, 0.5 );
		}

		

		int mz = glfwGetMouseWheel();
		
		zoom -= mz * 1.5;
		zoom = std::max<float>( 5.0f, zoom);
		zoom = std::min<float>( zoomMax, zoom);
		glfwSetMouseWheel(0);

		// todo fix code of fail:
		double desiredMSPF = 0.017;

		if( dt < desiredMSPF )
		{
			double timeSlept = std::max(0.0, desiredMSPF - dt);
			glfwSleep( timeSlept );
		}
	}
	
	void render()
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		setDepthProjection();
		glEnable(GL_DEPTH_TEST);
		glLoadIdentity();

		gluLookAt( camPos[0],camPos[1], zoom, 
				   camPos[0],camPos[1],0,
				   0.0, 1.0, 0.0);

		updateWorldMouseCoordinates();

		glPushMatrix();
			glPushAttrib(GL_POLYGON_BIT);
			glTranslatef( mousePointer.x(), mousePointer.y(), mousePointer.z() );
			glColor3f(1,0,0);
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			glRotatef( numframes, 0,1,1 );
			Shapes3D::DrawSphere(0.25, 5, 5);
			glPopAttrib();
		glPopMatrix();


		physWorld.draw();
		physWorld.debugDraw();
	
		setOrthoProjection();
		glLoadIdentity();

		char charBuf[200];
		sprintf_s( charBuf, 200, "fps = %f", fps );
		BitmapFont::Print(charBuf, 15,15,0xff0000);
		sprintf_s( charBuf, 200, "dt = %f", dt );
		BitmapFont::Print(charBuf, 15,15*2,0x00ff00);
		sprintf_s( charBuf, 200, "num body objects: %i", physWorld.bodyList.size() );
		BitmapFont::Print(charBuf, 15,15*5, 0xff000);

		sprintf_s( charBuf, 200, "num pairs: %i", physWorld.myGrid->numCollisionPairs );
		BitmapFont::Print(charBuf, 15,15*6, 0xff000);

		sprintf_s( charBuf, 200, "num boxes: %i", physWorld.myGrid->boxes.size() );
		BitmapFont::Print(charBuf, 15,15*7, 0xff000);

		sprintf_s( charBuf, 200, "Press ESC to quit!" );
		BitmapFont::Print(charBuf, 15,15*8, 0xffffff);	

		int timings = 1;
		for each(PerfTiming pf in physWorld.perfTimingList )
		{
			sprintf_s( charBuf, 200, pf.toString().c_str() );
			BitmapFont::Print(charBuf, 15,15*8 + timings*15, 0xffffff);	

			timings++;
		}

		
		//if( numframes % 2 == 0)
		{
			
			for(size_t i=millisHistogram.size()-1; i!=0; i--)
			{
				millisHistogram[i] = millisHistogram[i-1]; 
			}
			millisHistogram[0] = dt;
		}

		

		glColor3f(1,0,1);
		glLineWidth(2.0);
		glBegin(GL_LINES);
		for(size_t i=0; i<millisHistogram.size(); i++)
		{
			glVertex2f( i*4, yres-5 );
			glVertex2f( i*4, yres-5 - millisHistogram[i] * 1000*4 );
		}

		glEnd();
		glLineWidth(1.0);

	}

private:
	World physWorld;
	//SpringBody *player;
	Vec3f mousePointer;
	Vec2f mouseBeginPos;
	Vec2f camPos;

	std::vector<float> millisHistogram;

	float zoom;
	static const int zoomMax = 50;


};